<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="Drip Into A River Animation">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/DGDownloaderButton/">
<meta property="og:title" content="       Drip Into A River · Part I | Gua  ">
<meta property="og:description" content="Drip Into A River Animation">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       Drip Into A River · Part I | Gua  ">
<meta name="twitter:description" content="Drip Into A River Animation">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/DGDownloaderButton/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>Drip Into A River · Part I</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2016-06-28"> 2016-06-28
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
-->
      </span>
    </div>
  </header>
  <h2 id="sweet-ios-animation-plan">Sweet iOS Animation Plan</h2>
<p>给自己制定了一个很长远的动效学习计划，<strong><a href="https://github.com/Gua-Animation/Sweet-iOS-Animation">Sweet-iOS-Animation</a></strong>。其实就是促进喜欢写动效的同学，多多实现<a href="https://dribbble.com/">dribbble</a>上的优秀作品。我也会定期更新原型图作品表单，收集优秀的原型图。如果大家对此学习计划也有兴趣，可以从<a href="https://github.com/Gua-Animation/Sweet-iOS-Animation">这里</a>查看内容。</p>
<p>以上是对于学习计划的简介，下面开始这次动效分析。</p>
<h2 id="section">“水滴汇集”下载按钮概念图</h2>
<p>在dribbble上发现一个创意很好的下载按钮的创意，首先要感谢原型图作者<a href="https://dribbble.com/SamuraiChen">SamuraiChen</a>分享原型图<a href="https://dribbble.com/shots/2785355-Animation-on-a-concept-draft-downloads">Animation on a concept draft downloads</a>。我将这个特效起名为<em>Drip Into a River【水滴汇集】</em>。以下是原型图效果：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/DGDownloaderButton-img-1.gif" width="300px" /></p>
<h2 id="section-1">动画拆解</h2>
<p>这个动画我们主要关注的是<strong>水滴汇集部分</strong>，和<strong>下载完成后按钮的震动波扩散</strong>两个部分。先来说说<strong>水滴汇集动画</strong>。</p>
<h3 id="section-2">一、水滴汇集</h3>
<ul>
  <li>内部的下载状态实心圆要逐渐增大；</li>
  <li>外部水滴要以一个弧度飞入内部圆的圆心，并且接触时最好有粘黏效果；</li>
  <li>外部水滴的起始位置大概分布在内部圆最大轮廓线环形半径的2倍左右；</li>
  <li>外部水滴在飞行时，会有忽大忽小的效果。</li>
</ul>
<h3 id="section-3">二、震动波扩散</h3>
<ul>
  <li>整体按钮先略微缩小，准备突然增大并抖动；</li>
  <li>按钮变大，并带有弹簧效果，最终回复原大小；</li>
  <li>抖动最猛烈时，向四周发散“振动波”效果。</li>
</ul>
<p>我从中再次抽取主要的一些效果，进行实现。在制作的过程中，我遇到了很多问题，在本文最后部分进行讨论。</p>
<h2 id="section-4">利用贝塞尔曲线绘制水滴路径</h2>
<p>我绘制了一张静态描述图如下：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/DGDownloaderButton-img-2.jpeg" /></p>
<p>在图中，我们在最外围有一个有圆环，被称为<strong>水滴生成区</strong>。我们所有的水滴，为了保证在汇集飞行时候弯曲轨迹，所以<script type="math/tex">\overline{OC}</script>的距离（即水滴生成区圆环半径），我们需要保证。在<script type="math/tex">\overline{OC}</script>上，取中点<script type="math/tex">A</script>，并且选取长度为<script type="math/tex">h</script>的距离，确定<script type="math/tex">B</script>点，得到<script type="math/tex">\overline{BA} = h</script>。这里的<script type="math/tex">h</script>距离需要我们去尝试，<script type="math/tex">B</script>点也就是轨迹曲线的<em>Control Poiont</em>。</p>
<p>在我们写代码的时候，我们会已知圆心<script type="math/tex">O(x_1, y_1)</script>，以及水滴圆心<script type="math/tex">C(x_2, y_2)</script>，以及一些距离<script type="math/tex">\overline{OC} = radius\cdot k</script>（这里的<script type="math/tex">k</script>，我定义为主半径的<strong>倍数系数</strong>，只用于描述外围半径和内部圆的一个半径关系）。我们设待求点坐标为<script type="math/tex">B(x, y)</script>，对以上问题进行建笛卡尔坐标系求解。</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/DGDownloaderButton-img-3.jpeg" /></p>
<p>当然这之中，我们需要使用欧几里得距离法，将<script type="math/tex">d</script>求出。
\begin{align}
x = x_a - \overline{AM} = x_a - \frac{h}{d} \cdot \overline{CN} = x_a - \frac{h}{d} \cdot (y_2 - y_a)
\end{align}
\begin{align}
y = y_a - \overline{BM} = y_a - \frac{h}{d} \cdot \overline{AN} = y _a - \frac{h}{d} \cdot (x_2 - x_a)<br />
\end{align}</p>
<p>在推导公式后，也许你会发觉一个问题：<strong>水滴飞行轨迹在确定起始点后，可能会有两种</strong>。是的，这种想法是正确的，因为这两种贝塞尔轨迹是关于<script type="math/tex">\overline{OC}</script>对称的。在图中，我也给出了另外一种轨迹的<em>Congrol Point</em>位置。其实，我们发现，我们<strong>仅需要修改<script type="math/tex">h</script>的符号，就是另外一种轨迹的计算方法</strong>。</p>
<p>我在实例代码中，建立了一个<code>DGDownloaderMath.m</code>文件，这里就是存储在绘制贝塞尔曲线时，所用到的公式算法。下面代码片段是关于以上水滴飞行贝塞尔轨迹的实现方式：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">+</span> <span class="p">(</span><span class="no">CGPoint</span><span class="p">)</span><span class="ss">calcControlPoint</span><span class="p">:</span> <span class="p">(</span><span class="no">CGPoint</span><span class="p">)</span><span class="no">O1</span> <span class="ow">and</span><span class="p">:</span> <span class="p">(</span><span class="no">CGPoint</span><span class="p">)</span><span class="no">O2</span> <span class="ss">random</span><span class="p">:</span> <span class="p">(</span><span class="n">bool</span><span class="p">)</span><span class="n">isRandom</span> <span class="p">{</span>
    <span class="no">CGPoint</span> <span class="no">O_centre</span> <span class="o">=</span> <span class="no">CGPointMake</span><span class="p">((</span><span class="no">O1</span><span class="o">.</span><span class="n">x</span> <span class="o">+</span> <span class="no">O2</span><span class="o">.</span><span class="n">x</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="o">.</span><span class="n">f</span><span class="p">,</span> <span class="p">(</span><span class="no">O1</span><span class="o">.</span><span class="n">y</span> <span class="o">+</span> <span class="no">O2</span><span class="o">.</span><span class="n">y</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="o">.</span><span class="n">f</span><span class="p">);</span>
    <span class="no">CGFloat</span> <span class="n">d</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="ss">calcDistance</span><span class="p">:</span> <span class="no">O_centre</span> <span class="ss">to</span><span class="p">:</span> <span class="no">O1</span><span class="o">]</span><span class="p">;</span>
    <span class="no">CGFloat</span> <span class="n">k</span> <span class="o">=</span> <span class="n">d</span> <span class="o">/</span> <span class="mi">40</span><span class="o">.</span><span class="n">f</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">isRandom</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">int</span> <span class="n">isRandom_int</span> <span class="o">=</span> <span class="n">arc4random</span><span class="p">()</span> <span class="o">%</span> <span class="mi">2</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">isRandom_int</span><span class="p">)</span> <span class="n">k</span> <span class="o">=</span> <span class="o">-</span><span class="n">k</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="no">CGFloat</span> <span class="n">new_x</span> <span class="o">=</span> <span class="p">(</span><span class="no">O1</span><span class="o">.</span><span class="n">y</span> <span class="o">-</span> <span class="no">O2</span><span class="o">.</span><span class="n">y</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="o">.</span><span class="n">f</span> <span class="o">/</span> <span class="n">k</span> <span class="o">+</span> <span class="p">(</span><span class="no">O1</span><span class="o">.</span><span class="n">x</span> <span class="o">+</span> <span class="no">O2</span><span class="o">.</span><span class="n">x</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="o">.</span><span class="n">f</span><span class="p">;</span>
    <span class="no">CGFloat</span> <span class="n">new_y</span> <span class="o">=</span> <span class="o">-</span> <span class="p">((</span><span class="no">O1</span><span class="o">.</span><span class="n">x</span> <span class="o">-</span> <span class="no">O2</span><span class="o">.</span><span class="n">x</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="o">.</span><span class="n">f</span> <span class="o">/</span> <span class="n">k</span> <span class="o">-</span> <span class="p">(</span><span class="no">O1</span><span class="o">.</span><span class="n">y</span> <span class="o">+</span> <span class="no">O2</span><span class="o">.</span><span class="n">y</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="o">.</span><span class="n">f</span><span class="p">);</span>
    <span class="k">return</span> <span class="no">CGPointMake</span><span class="p">(</span><span class="n">new_x</span><span class="p">,</span> <span class="n">new_y</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<p>为了生成不同轨迹效果的样式，我使用了方法<code>arc4random()</code>来生成一个二进制随机数。剩下计算方法，严格遵循以上推导公式。</p>
<p>之后，我们要在视图层为水滴制作<strong>layer</strong>样式、设计动画并提供触发动画的入口方法。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#pragma mark - 动画开启入口</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span> <span class="n">startAnimation</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">gameTime</span> <span class="o">!=</span> <span class="kp">nil</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">[</span><span class="nb">self</span> <span class="n">stopAnimation</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">gameTime</span> <span class="o">=</span> <span class="o">[</span><span class="no">CADisplayLink</span> <span class="ss">displayLinkWithTarget</span><span class="p">:</span> <span class="nb">self</span>
                                                <span class="ss">selector</span><span class="p">:</span> <span class="vi">@selector</span><span class="p">(</span><span class="n">refreshAnimation</span><span class="p">)</span><span class="o">]</span><span class="p">;</span>
    <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">gameTime</span> <span class="ss">addToRunLoop</span><span class="p">:</span> <span class="o">[</span><span class="no">NSRunLoop</span> <span class="n">currentRunLoop</span><span class="o">]</span>
                        <span class="ss">forMode</span><span class="p">:</span> <span class="no">NSRunLoopCommonModes</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">#pragma mark - 动画关闭方法</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span> <span class="n">stopAnimation</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">坑点！</span>
    <span class="sr">//</span> <span class="err">安全释放</span><span class="no">DisplayLink</span><span class="err">切记要赋值</span><span class="kp">nil</span>
    <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">gameTime</span> <span class="n">invalidate</span><span class="o">]</span><span class="p">;</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">gameTime</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">#pragma mark - 刷新动画，判断是否需要增加动点</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span> <span class="n">refreshAnimation</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">self</span><span class="o">.</span><span class="n">isProgressing</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">[</span><span class="nb">self</span> <span class="n">stopAnimation</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">这里是</span><span class="no">CADisplayLink</span><span class="err">触发函数</span>
    <span class="sr">//</span> <span class="n">count</span><span class="err">属性为一个频率计数器</span>
    <span class="sr">//</span> <span class="err">并且最大值为</span><span class="mi">49</span><span class="err">，我可以通过控制周期最大数值，</span>
    <span class="sr">//</span> <span class="err">从而影响生成新小球的生成频率</span>
    <span class="n">_count</span> <span class="o">++</span><span class="p">;</span> <span class="n">_count</span> <span class="sx">%= 50;</span>
<span class="sx">    if (_count =</span><span class="o">=</span> <span class="mi">40</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">[</span><span class="nb">self</span> <span class="ss">readyPointAnimation</span><span class="p">:</span> <span class="o">[</span><span class="no">DGDownloaderMath</span> <span class="ss">calcBeginPoint</span><span class="p">:</span> <span class="nb">self</span><span class="o">.</span><span class="n">circlePoint</span>
                                                             <span class="ss">radius</span><span class="p">:</span> <span class="nb">self</span><span class="o">.</span><span class="n">circleRadius</span>
                                                         <span class="ss">coefficent</span><span class="p">:</span> <span class="mi">2</span><span class="o">.</span><span class="mi">0</span><span class="n">f</span><span class="o">]]</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="c1">#pragma mark - 进入动画，传入起始坐标点</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span> <span class="ss">readyPointAnimation</span><span class="p">:</span> <span class="p">(</span><span class="no">CGPoint</span><span class="p">)</span> <span class="n">center</span> <span class="p">{</span>
    <span class="no">CGFloat</span> <span class="n">pointRadius</span> <span class="o">=</span> <span class="mi">8</span><span class="o">.</span><span class="n">f</span><span class="p">;</span>
    <span class="no">CALayer</span> <span class="o">*</span><span class="n">shape</span> <span class="o">=</span> <span class="o">[[</span><span class="no">CALayer</span> <span class="n">alloc</span><span class="o">]</span> <span class="n">init</span><span class="o">]</span><span class="p">;</span>
    <span class="n">shape</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="no">DGDownloaderButtonDefaultColor</span><span class="o">.</span><span class="n">CGColor</span><span class="p">;</span>
    <span class="n">shape</span><span class="o">.</span><span class="n">cornerRadius</span> <span class="o">=</span> <span class="n">pointRadius</span><span class="p">;</span>
    <span class="n">shape</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="no">CGRectMake</span><span class="p">(</span><span class="n">center</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="n">center</span><span class="o">.</span><span class="n">y</span><span class="p">,</span> <span class="n">pointRadius</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">pointRadius</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
    <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">layer</span> <span class="ss">addSublayer</span><span class="p">:</span> <span class="n">shape</span><span class="o">]</span><span class="p">;</span>
    <span class="o">[</span><span class="nb">self</span> <span class="ss">runPointAnimation</span><span class="p">:</span> <span class="n">shape</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">#pragma mark - 启动动画，向中心吸收</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span> <span class="ss">runPointAnimation</span><span class="p">:</span> <span class="p">(</span><span class="no">CALayer</span> <span class="o">*</span><span class="p">)</span><span class="n">point</span> <span class="p">{</span>
    <span class="no">CAKeyframeAnimation</span> <span class="o">*</span><span class="n">keyAnimation</span> <span class="o">=</span> <span class="o">[</span><span class="no">CAKeyframeAnimation</span> <span class="ss">animationWithKeyPath</span><span class="p">:</span> <span class="err">@</span><span class="s2">&quot;position&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="n">keyAnimation</span><span class="o">.</span><span class="n">path</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="ss">makePointPath</span><span class="p">:</span> <span class="n">point</span><span class="o">].</span><span class="n">CGPath</span><span class="p">;</span>
    <span class="n">keyAnimation</span><span class="o">.</span><span class="n">fillMode</span> <span class="o">=</span> <span class="n">kCAFillModeForwards</span><span class="p">;</span>
    <span class="n">keyAnimation</span><span class="o">.</span><span class="n">timingFunction</span> <span class="o">=</span> <span class="o">[</span><span class="no">CAMediaTimingFunction</span> <span class="ss">functionWithName</span><span class="p">:</span> <span class="n">kCAMediaTimingFunctionEaseIn</span><span class="o">]</span><span class="p">;</span>
    <span class="n">keyAnimation</span><span class="o">.</span><span class="n">duration</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
    <span class="n">keyAnimation</span><span class="o">.</span><span class="n">removedOnCompletion</span> <span class="o">=</span> <span class="no">NO</span><span class="p">;</span>
    <span class="o">[</span><span class="n">point</span> <span class="ss">addAnimation</span><span class="p">:</span> <span class="n">keyAnimation</span> <span class="ss">forKey</span><span class="p">:</span> <span class="err">@</span><span class="s2">&quot;moveAnimation&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">这里使用</span><span class="no">GCD</span><span class="err">，是为了将水滴的</span><span class="n">layer</span><span class="err">删去</span>
    <span class="sr">//</span> <span class="err">因为在</span><span class="n">fillMode</span><span class="err">属性为</span><span class="n">kCAFillModeForwards</span>
    <span class="sr">//</span> <span class="n">removedOnCompletion</span><span class="err">为</span><span class="no">NO</span><span class="err">的时候</span>
    <span class="sr">//</span> <span class="err">水滴在动画结束后会保持在圆心的状态</span>
    <span class="sr">//</span> <span class="err">我们需要使用其他方法来将其删除</span>
    <span class="sr">//</span> <span class="err">否则会在中心进度刚开始时，影响效果</span>
    <span class="n">double</span> <span class="n">delay</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
    <span class="n">dispatch_time_t</span> <span class="n">popTime</span> <span class="o">=</span> <span class="n">dispatch_time</span><span class="p">(</span><span class="no">DISPATCH_TIME_NOW</span><span class="p">,</span> <span class="p">(</span><span class="n">int64_t</span><span class="p">)(</span><span class="n">delay</span> <span class="o">*</span> <span class="no">NSEC_PER_SEC</span><span class="p">));</span>
    <span class="n">dispatch_after</span><span class="p">(</span><span class="n">popTime</span><span class="p">,</span> <span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
        <span class="o">[</span><span class="n">point</span> <span class="n">removeFromSuperlayer</span><span class="o">]</span><span class="p">;</span>
    <span class="p">});</span>
<span class="p">}</span>
<span class="c1">#pragma mark - 生成曲线路径</span>
<span class="o">-</span> <span class="p">(</span><span class="no">UIBezierPath</span> <span class="o">*</span><span class="p">)</span> <span class="ss">makePointPath</span><span class="p">:</span> <span class="p">(</span><span class="no">CALayer</span> <span class="o">*</span><span class="p">)</span><span class="n">point</span> <span class="p">{</span>
    <span class="no">UIBezierPath</span> <span class="o">*</span><span class="n">path</span> <span class="o">=</span> <span class="o">[</span><span class="no">UIBezierPath</span> <span class="n">bezierPath</span><span class="o">]</span><span class="p">;</span>
    <span class="o">[</span><span class="n">path</span> <span class="ss">moveToPoint</span><span class="p">:</span> <span class="n">point</span><span class="o">.</span><span class="n">position</span><span class="o">]</span><span class="p">;</span>
    <span class="o">[</span><span class="n">path</span> <span class="ss">addQuadCurveToPoint</span><span class="p">:</span> <span class="nb">self</span><span class="o">.</span><span class="n">circlePoint</span>
                 <span class="ss">controlPoint</span><span class="p">:</span> <span class="o">[</span><span class="no">DGDownloaderMath</span> <span class="ss">calcControlPoint</span><span class="p">:</span> <span class="nb">self</span><span class="o">.</span><span class="n">circlePoint</span>
                                                              <span class="ow">and</span><span class="p">:</span> <span class="n">point</span><span class="o">.</span><span class="n">position</span>
                                                           <span class="ss">random</span><span class="p">:</span> <span class="no">YES</span><span class="o">]]</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">path</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/DGDownloaderButton-img-4.gif" width="280px" /></p>
<p>以上便是水滴汇集动画的过程解析，这个效果也是整个作品的主要部分。在整体制作过程，将知识组合使用，想必肯定能锻炼自己很多方面的能力。在下一篇，我将会主要讲解结束动画中的<strong>“振动波”效果</strong>，尽请期待。</p>
<hr />
<p><a href="https://github.com/dgytdhy/DGDownloaderButton">DGDownloaderButton Source Code</a></p>
<p><a href="http://objccn.io/issue-12-1/">Objc中国·动画解释</a></p>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《Drip Into A River · Part I》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/DGDownloaderButton/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/DGDownloaderButton/&amp;title=《Drip Into A River · Part I》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/DGDownloaderButton/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/DGDownloaderButton/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>